extern crate derive_more;

use std::fmt;
use std::io::{Error, ErrorKind};

/// Traits for RGB u8s and HSV f32s to share
pub trait UpdateColor<T, U> {
    /// handle incrementing the values
    fn increment(&mut self, _: U);
    /// prints the maximum range
    fn max(&self) -> T;
    ///Ensure the range is never exceeded
    fn clamp(&mut self, _: U);
    ///Replace the value
    fn update(&mut self, _: T);
}

#[derive(Copy, Clone, Debug, PartialEq)]
/// Newtype for u8
pub struct Value(u8);

#[derive(Copy, Clone, Debug, PartialEq)]
/// Newtype for f32
pub struct FloatValue(f32, f32);

/// Clamps f32 to the range
impl FloatValue {
    fn new(val: f32, max: f32) -> Self {
        let mut new = FloatValue(val, max);
        new.update(val);
        new
    }
}

impl UpdateColor<u8, i16> for Value {
    fn increment(&mut self, inc: i16) {
        let new_value = self.0 as i16 + inc;
        self.clamp(new_value);
    }

    fn max(&self) -> u8 {
        255
    }

    fn update(&mut self, new: u8) {
        self.0 = new;
    }

    fn clamp(&mut self, input: i16) {
        if input > 255 {
            self.0 = 255;
        } else if input < 0 {
            self.0 = 0;
        } else {
            self.0 = input as u8;
        }
    }
}

impl UpdateColor<f32, f32> for FloatValue {
    fn increment(&mut self, inc: f32) {
        let new_value = self.0 + inc;
        self.clamp(new_value);
    }

    fn max(&self) -> f32 {
        self.1
    }

    fn update(&mut self, new: f32) {
        self.clamp(new);
    }

    ///Clamp for s, v percentages, rollover for h angle
    fn clamp(&mut self, input: f32) {
        self.0 = match input {
            y if y.is_nan() => 0.0,
            _y if self.1 as i32 == 360 => (input + 360.0) % 360.0,
            _y if input > self.1 => self.1,
            y if y < 0.0 => 0.0,
            _ => input,
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct RGB {
    pub red: Value,
    pub green: Value,
    pub blue: Value,
}

/// RGB color object
impl RGB {
    pub fn new(red: u8, green: u8, blue: u8) -> Self {
        RGB {
            red: Value(red),
            green: Value(green),
            blue: Value(blue),
        }
    }

    /// Convert an RGB from an HSV
    pub fn from_hsv(hsv: HSV) -> Self {
        RGB::hsv_to_rgb(hsv.hue(), hsv.saturation(), hsv.value())
    }

    ///Return the raw u8
    pub fn red(&self) -> u8 {
        self.red.0
    }

    pub fn green(&self) -> u8 {
        self.green.0
    }

    pub fn blue(&self) -> u8 {
        self.blue.0
    }

    /// Create the hex string
    pub fn hex(&self) -> String {
        format!("{:02x}{:02x}{:02x}", self.red(), self.green(), self.blue())
    }

    /// HSV to RGB logic, absracted for crate use later, sourced from wikipedia
    pub fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> RGB {
        let chroma = value * saturation;
        let hue_derivative = hue / 60.0;

        let x = chroma * (1.0 - (hue_derivative % 2.0 - 1.0).abs());

        let [r1, g1, b1] = match hue_derivative {
            y if y.is_nan() => [chroma, x, 0.0],
            y if (0.0..1.0).contains(&y) => [chroma, x, 0.0],
            y if (1.0..2.0).contains(&y) => [x, chroma, 0.0],
            y if (2.0..3.0).contains(&y) => [0.0, chroma, x],
            y if (3.0..4.0).contains(&y) => [0.0, x, chroma],
            y if (4.0..5.0).contains(&y) => [x, 0.0, chroma],
            y if (5.0..6.0).contains(&y) => [chroma, 0.0, x],
            _ => [0.0, 0.0, 0.0],
        };

        let m = value - chroma;
        let [r, g, b] = [r1 + m, g1 + m, b1 + m];

        RGB::new(
            (r * 255.0).round() as u8,
            (g * 255.0).round() as u8,
            (b * 255.0).round() as u8,
        )
    }
}

/// For printing RGB easily
impl fmt::Display for RGB {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Use `self.number` to refer to each positional data point.
        write!(f, "({}, {}, {})", self.red(), self.green(), self.blue())
    }
}

/// convert to RGB from 6 digit hex string
impl std::str::FromStr for RGB {
    type Err = std::io::Error;

    /// sloppy way to turn hex strings into rgb objects
    fn from_str(s: &str) -> Result<Self, std::io::Error> {
        let mut characters: Vec<char> = Vec::new();
        for c in s.chars() {
            assert!(c.is_ascii_hexdigit());
            characters.push(c);
        }
        if characters.len() != 6 {
            return Err(Error::new(
                ErrorKind::Other,
                "enter a 6-digit hexadecimal string",
            ));
        }
        let mut values: Vec<u8> = Vec::new();
        for chunk in characters.chunks(2) {
            let val: String = chunk.to_owned().into_iter().collect();
            values.push(u8::from_str_radix(&val, 16).unwrap());
        }
        return Ok(RGB::new(values[0], values[1], values[2]));
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct HSV {
    pub hue: FloatValue,
    pub saturation: FloatValue,
    pub value: FloatValue,
}

impl HSV {
    pub fn new(hue: f32, saturation: f32, value: f32) -> Self {
        return HSV {
            hue: FloatValue::new(hue, 360.0),
            saturation: FloatValue::new(saturation, 1.0),
            value: FloatValue::new(value, 1.0),
        };
    }

    /// Create an HSV from an RGB
    pub fn from_rgb(rgb: RGB) -> Self {
        let [r, g, b] = [rgb.red(), rgb.green(), rgb.blue()];
        HSV::rgb_2_hsv(r, g, b)
    }

    pub fn hue(&self) -> f32 {
        return self.hue.0;
    }
    pub fn saturation(&self) -> f32 {
        return self.saturation.0;
    }
    pub fn value(&self) -> f32 {
        return self.value.0;
    }

    /// create HSV from RGB, logic abstracted for crate use
    pub fn rgb_2_hsv(r: u8, g: u8, b: u8) -> Self {
        let [r_float, g_float, b_float] = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0];

        let mut sorted = vec![r, g, b];

        sorted.sort();

        let x_max: f32 = sorted[2] as f32 / 255.0;
        let x_min: f32 = sorted[0] as f32 / 255.0;
        let chroma = x_max - x_min;

        if sorted[2] == 0 {
            return HSV::new(0.0, 0.0, 0.0);
        }
        let value = x_max;
        let hue: f32 = match sorted[2] {
            _m if chroma == 0.0 => 0.0,
            m if m == r => HSV::derive_hue(g_float, b_float, chroma, 6.0),
            m if m == g => HSV::derive_hue(b_float, r_float, chroma, 2.0),
            m if m == b => HSV::derive_hue(r_float, g_float, chroma, 4.0),
            _ => 0.0,
        };
        let saturation: f32 = match value {
            _y if (value * 1000.0) as i32 == 0 => 0.0,
            _ => chroma / value,
        };

        HSV::new(hue, saturation, value)
    }

    /// Formula sourced from wikipedia
    fn derive_hue(a: f32, b: f32, chroma: f32, base: f32) -> f32 {
        let hue: f32 = 60.0 * ((a - b) / chroma + base);
        return (hue) % 360.0;
    }
}

/// For easy printing
impl fmt::Display for HSV {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Use `self.number` to refer to each positional data point.
        write!(
            f,
            "({}, {}, {})",
            self.hue(),
            self.saturation(),
            self.value()
        )
    }
}
